﻿#include "stdafx.h"
#if _QG_USE_GL
#include "qg_ogl_dev.h"
#include "qg_ogl_dev_ext.h"
#include "qg_ogl_cnv.h"
#include "qg_ogl_mtrl.h"
#include "qg_ogl_draw.h"
#include "qg_trace.h"
#include "qg_math.h"

#ifdef _MSC_VER
#	pragma comment(lib, "opengl32")
#endif

using namespace _ogl;

//////////////////////////////////////////////////////////////////////////
// GL 디바이스
KIMPL_SUBCLASS(GLDevice, "RenderDeviceGL");

namespace _ogl
{
	kType DevTypeGL()
	{
		return GLDevice::Type();
	}
}

//
void GLDevice::TypeInitialize(kType type) throw()
{
}

//
void GLDevice::TypeFinalize(kType type) throw()
{
}

//
GLDevice::GLDevice()
{
}

//
GLDevice::~GLDevice()
{
	//
	_Finalize();

#if _SB_WINDOWS_ && !_QG_USE_SDL
	if (_hrc)
	{
		if (!wglMakeCurrent(NULL, NULL) || !wglDeleteContext(_hrc))
			kobj_tracep(KLV_WARNING, KERR_CLEAN);
	}

	if (_stub)
	{
		if (_hdc)
		{
			HWND hwnd = (HWND)_stub->GetHandle();
			ReleaseDC(hwnd, _hdc);
		}

		if (_stub->GetFlags(QGSDF_FULL))
		{
			ChangeDisplaySettings(NULL, 0);
			ShowCursor(TRUE);
		}
	}
#endif
}

//
bool GLDevice::_Initialize(qgStub* stub, kint flags)
{
#if _SB_WINDOWS_ && !_QG_USE_SDL
	tpl::Point size(stub->GetSize());
	GLint pfmt;

	// 전체 화면 테스트
	if (K_OFLAG(flags, QGSDF_FULL))
	{
		DEVMODE dm = {0, };

		if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm))
		{
			// 설마 이 함수가 실패하겠냐만 서는...
			kobj_tracep(KLV_ERROR, KERR_FAIL);
			return false;
		}

		dm.dmPelsWidth = size.x;
		dm.dmPelsHeight = size.y;
		dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

		if ((pfmt = ChangeDisplaySettings(&dm, CDS_FULLSCREEN)) != DISP_CHANGE_SUCCESSFUL)
		{
			// 전체 화면으로 바꾸지 못함
			kobj_tracep(KLV_ERROR, KERR_FAIL);
			return false;
		}
	}

	// 포맷 정보
	PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR), 1, };

	// PFD_STEREO 스테레오 디스플레이. 즉, 3D...도 있는데 구현 귀찮...
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;

	if (!stub->GetInsts(QGSDI_EXF_1))
		pfd.dwFlags |= PFD_DOUBLEBUFFER;

	if (K_OFLAG(flags, QGSDF_BGRA) || !K_OFLAG(flags, QGSDF_DITHER))
	{
		pfd.cColorBits = 32;
		pfd.cDepthBits = 32;
	}
	else
	{
		pfd.cColorBits = 16;
		pfd.cDepthBits = 16;
	}

	pfd.dwLayerMask = PFD_MAIN_PLANE;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cAccumBits = 0;
	pfd.cStencilBits = 1;

	// 포맷 결정
	HWND hwnd = (HWND)stub->GetHandle();
	HDC hdc = GetDC(hwnd);

	// 멀티샘플링
	bool canmsaa = false;
	int msaafmt = 0;

	if (K_OFLAG(flags, QGSDF_MSAA))
	{
		// 아래꺼 NULL. 여기서 하는게 아닌게 확실한데...
		PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
		if (wglChoosePixelFormatARB)
		{
			int iAttributes[] =
			{
				WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
				WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
				WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
				WGL_COLOR_BITS_ARB, 24,
				WGL_ALPHA_BITS_ARB, 8,
				WGL_DEPTH_BITS_ARB, 16,
				WGL_STENCIL_BITS_ARB, 0,
				WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
				WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
				WGL_SAMPLES_ARB, 4, // <- 이 숫자가 샘플링개수
				0, 0
			};
			float fAttributes[] = {0, 0};
			int pixelfmt;
			UINT count;

			for (kint sample = 8; sample >= 2; sample -= 2)
			{
				iAttributes[19] = sample;

				if (wglChoosePixelFormatARB(hdc, iAttributes, fAttributes, 1, &pixelfmt, &count) &&
					count > 0)
				{
					canmsaa = true;
					msaafmt = pixelfmt;

					break;
				}
			}
		}
	}

	// 포맷 선택
	if (!canmsaa || !SetPixelFormat(hdc, msaafmt, &pfd))
	{
		// MSAA
		if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
		{
			// 스텐실 꺼봄
			pfd.cStencilBits = 0;
			if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
			{
				// Z버퍼 24비트
				pfd.cDepthBits = 24;
				if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
				{
					// Z버퍼 16비트
					pfd.cDepthBits = 16;
					if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
					{
						// 진행불가!!!
						// OPENGL 드라이버에 문제가 있는 듯
						kobj_tracep(KLV_ERROR, KERR_PROPERTY);
						return false;
					}
				}
			}
		}
	}

	// 컨텍스트
	HGLRC hrc;

#ifdef WGL_ARB_create_context
	PFNWGLCREATECONTEXTATTRIBSARBPROC proc_create_context_attribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
	if (proc_create_context_attribs)
	{
		// 버전 2.1 사용하면 되나?
		// -> 버쳘 박스가 2.1이더라...
		int atcs[] =
		{
			WGL_CONTEXT_MAJOR_VERSION_ARB, 2,
			WGL_CONTEXT_MINOR_VERSION_ARB, 1,
			0
		};

		hrc = proc_create_context_attribs(hdc, 0, atcs);
	}
	else
#endif
	{
		// ARB가 없거나, 실패
		hrc = wglCreateContext(hdc);
	}

	if (!hrc || !wglMakeCurrent(hdc, hrc))
	{
		kobj_tracep(KLV_ERROR, KERR_FAIL_CREATE);
		return FALSE;
	}

	// 정리
	_hdc = hdc;
	_hrc = hrc;
	_pfd = pfd;

	_caps.val_depth_bpp = pfd.cDepthBits;
#else
	_caps.val_depth_bpp = K_OFLAG(flags, QGSDF_BGRA) ? 24 : K_OFLAG(flags, QGSDF_DITHER) ? 16 : 24;
#endif	// _SB_WINDOWS_ && !_QG_USE_SDL

	// GL 확장 기능 초기화
	InitializeExtension();

	if (K_OFLAG(flags, QGSDF_INFO))
		QglTestGlFunc();

	// 기능 확인
	InitializeCaps(_caps, true);

	if (_caps.version < 200)
	{
		// 엑... 버전 2.0은 되어야 쓸 수 있음
		kobj_tracef(KLV_ERROR, "need OPENGL version 2.0 or higher. your system has %.2f", (float)_caps.version / 100.0f);
		return false;
	}

	// 1바이트 짜리 텍스쳐로 글자 못 그린다.
	_caps.supp_byte_glyph_texture = false;

	// 비월주사... 가 아니고 수직 동기화 인터벌
	QglSwapInterval(0);

	// 시작함
	kobj_trace(KLV_INFO, "OPENGL Driver");
	kobj_tracef(KLV_INFO, " - OpenGL version %.2f, GLSL version %.2f", (float)_caps.version / 100.0f, (float)_caps.version_shader / 100.0f);
	kobj_tracef(KLV_INFO, " - Driver: %s", _caps.renderer);
	kobj_tracef(KLV_INFO, " - Vendor: %s", _caps.vendor);

	// 슈퍼
	return super_type::_Initialize(stub, flags);
}

//
bool GLDevice::_ResetPrepare(const tpl::Point& size, bool prepare)
{
	kint i;

	// GL용으로 준비
	_sprn = &_vprn;
	_sdeb = &_vdeb;

	// 기본값
	super_type::_ResetPrepare(size, prepare);

	// TM 디바이스에 달린 속성 처리.
	GLDevice::SetTM(QGTMS_PROJ, tpl::Mat4::PerspectiveFovLH(KM_PI_HOH, size.Aspect(), 1.0f, 100000.0f));
	_trfm.tfm = _math::BuildIrrchtTextureMatrix(0.0f, tpl::Vec2(0.0f, 0.0f), tpl::Vec2(0.0f, 1.0f), tpl::Vec2(1.0f, -1.0f));

	// 픽셀 저장 정렬
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	// 광원 
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, _sprn->l_ambi.ToPointer());
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, (const GLfloat*)&tpl::Colors::White);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (const GLfloat*)&tpl::Colors::White);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, (const GLfloat*)&tpl::Colors::Black);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (const GLfloat*)&tpl::Colors::White);

#ifdef GL_EXT_separate_specular_color
	if (TestGLCS(QGLX_EXT_SEPARATE_SPECULAR_COLOR))
		glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SINGLE_COLOR);	// 만약 shine>0.0f 이면 GL_SEPARATE_SPECULAR_COLOR
#endif

	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);
	glDisable(GL_LIGHTING);

	// 재질 알파 연산 및 테스트 값
	glAlphaFunc(GL_GREATER, 0.0f);

	// 샘플러 & 텍스쳐 & 팩터
	for (_vprn.m_tex_active = -1, i = 0; i < _caps.max_texture_count; i++)
	{
		if (!_ActiveTexture(i))
			break;

		_vprn.m_tex_type[i] = 0;

		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	// 래스터라이저
	_vprn.m_rasz_data.fill = QGFILL_SOLID;
	_vprn.m_rasz_data.cull = QGCULL_BACK;
	_vprn.m_rasz_data.dfunc = QGCOP_LEQ;
	_vprn.m_rasz_data.dwrite = true;
	_vprn.m_rasz_data.dbias = 0;
	_vprn.m_rasz_data.dclamp = 0.0f;
	_vprn.m_rasz_data.slope_scale = 0.0f;

	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glCullFace(GL_BACK);
	glDepthFunc(GL_LEQUAL);
	glDepthMask(GL_TRUE);
	glDisable(GL_POLYGON_OFFSET_POINT);
	glDisable(GL_POLYGON_OFFSET_LINE);
	glDisable(GL_POLYGON_OFFSET_FILL);
	//glPolygonOffset(0.0f, 0.0f);

	// 블랜드	
	_vprn.m_blnd_data.blend.op = QGBLO_NONE;
	_vprn.m_blnd_data.blend.src = QGBLV_NONE;
	_vprn.m_blnd_data.blend.dest = QGBLV_NONE;
	_vprn.m_blnd_data.alpha.op = QGBLO_NONE;
	_vprn.m_blnd_data.alpha.src = QGBLV_NONE;
	_vprn.m_blnd_data.alpha.dest = QGBLV_NONE;
	_vprn.m_blnd_data.mask = QGMASK_ALL;
	_vprn.m_blnd_blend = false;
	_vprn.m_blnd_alpha = false;

	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glEnable(GL_BLEND);
	QglBlendEquation(GL_FUNC_ADD);
	QglBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
	glDisable(GL_BLEND);

	// 그리기 레이아웃에서 사용하는 상태값
	for (i = 0; i < (kint)K_COUNTOF(_vprn.ws_client); i++)
	{
		_vprn.ws_client[i] = -1;
		_vprn.ws_custom[i] = -1;
	}

	// 평면
	k_assert_eq(_plns, NULL);
	_plns = k_new_zero(_caps.max_clip_planes, Plane);

	// 모드
	_vprn.r_mode = QGRMT_NONE;

	// 그 밖의 설정
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glHint(GL_POINT_SMOOTH_HINT, GL_FASTEST);
	glFrontFace(GL_CW);

	// 플랫 색깔 스키마를 DirectX와 맞춘다는데 뭘까.
#if defined(GL_ARB_provoking_vertex) || defined(GL_EXT_provoking_vertex)
	QglProvokingVertex(GL_FIRST_VERTEX_CONVENTION_EXT);
#endif

	// 인터페이스 준비
	if (prepare)
	{
		// 엄따...
	}

	// 그릴 수 있도록 함 (+뷰포트) (+씬그리기)
	_SetRenderTarget(NULL);

	return true;
}

// 빠이날
void GLDevice::_Finalize()
{
	// 기본 오브젝트 제거
	// 엄네...

	// 평면
	k_delete(_plns);

	//
	super_type::_Finalize();
}

//
GLenum GLDevice::_GetError(const char** str, bool output)
{
	GLenum err = glGetError();
	const char* msg;

	if (output || str)
	{
		switch (err)
		{
			case GL_NO_ERROR:
				msg = NULL;
				break;

			case GL_INVALID_ENUM:
				msg = "Invalid enum";
				break;

			case GL_INVALID_VALUE:
				msg = "Invalid value";
				break;

			case GL_INVALID_OPERATION:
				msg = "Invalid operation";
				break;

			case GL_STACK_OVERFLOW:
				msg = "Stack overflow";
				break;

			case GL_STACK_UNDERFLOW:
				msg = "Stack underflow";
				break;

			case GL_OUT_OF_MEMORY:
				msg = "Out of memory";
				break;

#ifdef GL_EXT_framebuffer_object
			case GL_INVALID_FRAMEBUFFER_OPERATION_EXT:
				msg = "Invalid frame buffer operation ext";
				break;
#endif

			default:
				msg = "Unknown";
				break;
		}

		if (output && msg)
			kobj_tracef(KLV_ERROR, "OpenGL#%d: %s\n", err, msg);

		if (str)
			*str = msg;
	}

	return err;
}

// 레이아웃 처리
kbool GLDevice::Layout(const qgSevLayout& e)
{
	if (!super_type::Layout(e))
		return false;

	kGuardSpinLock(_dlock)
	{
		// 이거하는데, 잠글 필요가 있낭...
		glViewport(0, 0, e.width, e.height);
	}

	GLDevice::SetTM(QGTMS_PROJ, tpl::Mat4::PerspectiveFovLH(KM_PI_HOH, _trfm.vp.aspect, 1.0f, 100000.0f));
	GLDevice::SetTM(QGTMS_VIEW, tpl::Mat4::IdentifyMatrix());
	GLDevice::SetTM(QGTMS_WORLD, tpl::Mat4::IdentifyMatrix());

	_trfm.otm = tpl::Mat4::OrthoLH(_vsize.width, -_vsize.height, -1.0f, 1.0f);
	_trfm.otm.ReplaceLocation(-1.0f, 1.0f, 0.0f);

	_vprn.r_mode = QGRMT_NONE;

	return true;
}

// 클리어
void GLDevice::_ClearDevice(kint clearance, const tpl::Color& color, kint stencil, float depth)
{
	kGuardSpinLock(_dlock)
	{
		GLbitfield cf = 0;

		if (clearance&QGCLEAR_STENCIL)
		{
			glClearStencil(stencil);
			cf |= GL_STENCIL_BUFFER_BIT;
		}

		if (clearance&QGCLEAR_DEPTH)
		{
			glClearDepth(depth);	// ES2에서는 glClearDepthf(1.0f)로 해야할 걸
			cf |= GL_DEPTH_BUFFER_BIT;

			if (!_vprn.m_rasz_data.dwrite)
			{
				// [2014-1-13 KIM] 이거 안하면 깊이 버퍼 클리어 안됨... 한참 헤멤. ㅠ_ㅜ
				glDepthMask(GL_TRUE);
				_vprn.m_rasz_data.dwrite = true;
			}
		}

		if (clearance&QGCLEAR_RENDER)
		{
			glClearColor(color.r, color.g, color.b, color.a);
			cf |= GL_COLOR_BUFFER_BIT;
		}

		if (cf)
			glClear(cf);
	}
}

//
kbool GLDevice::Enter(kint clearance)
{
	super_type::Enter(clearance);

	_ClearDevice(clearance, _sprn->cr_bg, 0, 1.0f);

#if _QG_USE_SDL
	glFrontFace(GL_CW);
#endif

	return true;
}

//
kbool GLDevice::Leave()
{
	// 끝내면서 여차저차 오류를 막자
	kGuardSpinLock(_dlock)
	{
		_sprn->m_rasz = NULL;
		_sprn->m_blnd = NULL;
		_vprn.r_mode = QGRMT_NONE;
	}

	return super_type::Leave();
}

//
kbool GLDevice::Flush()
{
	super_type::Flush();

	bool ret = true;

	kGuardSpinLock(_dlock)
	{
		glFlush();

#if _QG_USE_SDL
		SDL_GL_SwapBuffers();
#elif _SB_WINDOWS_
		if (!SwapBuffers(_hdc))
			ret = false;
#elif _SB_APPLE_
#error need fix
		glFinish();
		CGLFlushDrawable(...);
#else
		if (!glXSwapBuffers(_display, _drawable))
			ret = false;
#endif
	}

	return ret;
}

//
kbool GLDevice::SetTM(qgTmType nth, const tpl::Mat4& m)
{
	kint i;

	switch (nth)
	{
		case QGTMS_VIEW:
			_trfm.invv = m.Inverse();
			_trfm.vipr = m * _trfm.tm[QGTMS_PROJ];

		case QGTMS_WORLD:
			_trfm.tm[nth] = m;

			glMatrixMode(GL_MODELVIEW);
			glLoadMatrixf(_trfm.tm[QGTMS_VIEW].ToPointer());
			glMultMatrixf(_trfm.tm[QGTMS_WORLD].ToPointer());

			for (i = 0; i < _caps.max_clip_planes; i++)
			{
				if (_plns[i].enable)
					_UpdateClipPlane(i);
			}
			break;

		case QGTMS_PROJ:
			_trfm.vipr = _trfm.tm[QGTMS_VIEW] * m;
			_trfm.tm[QGTMS_PROJ] = m;

			glMatrixMode(GL_PROJECTION);
			glLoadMatrixf(m.ToPointer());
			break;

		default:
			if (nth >= QGTMS_MAX_VALUE)
				return false;
			else
			{
				i = (kint)((kuint)nth - QGTMS_TEX_0);
				bool rtt = _sprn->m_tex[i] && _sprn->m_tex[i]->IsOff();

				_ActiveTexture(i);
				glMatrixMode(GL_TEXTURE);

				if (!rtt && m.IsIdentity())
				{
					_trfm.tm[nth] = m;
					glLoadIdentity();
				}
				else
				{
					tpl::Mat4 gm = _math::BuildGlTextureMatrix(!rtt ? m : m * _trfm.tfm);
					_trfm.tm[nth] = gm;
					glLoadMatrixf(gm.ToPointer());
				}
			}
			break;
	}

	return true;
}

//
void GLDevice::Draw(kint vertexcount, kint vertexstart)
{
	if (!_sprn->w_lo || !_sprn->w_vb[0])
	{
		// 꼭지점 데이터가 없다
		kobj_tracep(KLV_ERROR, KERR_ARGUMENT);
	}
	else
	{
		// 그리기 모드
		_RenderScene();

		kGuardSpinLock(_dlock)
		{
			// 설정
			if (!_BindMtrl((ksize_t)_caps.max_texture_count) ||
				!_BindShader() ||
				!_BindDraw())
			{
				// 실패
				kobj_tracep(KLV_ERROR, KERR_INVAL);
			}
			else
			{
				// 그리기 호출
				glDrawArrays(GLConvert::Topology(_sprn->w_tpg), vertexstart, vertexcount);

				// 바인드 해제
				_UnbindDraw();
			}
		}
	}
}

//
void GLDevice::DrawIndexed(kint indexcount, kint vertexstart)
{
	if (!_sprn->w_lo || !_sprn->w_vb[0] || !_sprn->w_ib)
	{
		// 꼭지점, 인덱스 데이터가 없다
		kobj_tracep(KLV_ERROR, KERR_ARGUMENT);
	}
	else
	{
		// 그리기 모드
		_RenderScene();

		kGuardSpinLock(_dlock)
		{
			// 설정
			if (!_BindMtrl((ksize_t)_caps.max_texture_count) ||
				!_BindShader() ||
				!_BindDraw())
			{
				// 실패
				kobj_tracep(KLV_ERROR, KERR_INVAL);
			}
			else
			{
				// 인덱스 
				GLBuffer* ib = (GLBuffer*)_sprn->w_ib;
				GLenum indextype = ib->GetStride() == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
				QglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)ib->GetDescription());

				// 그리기 호출
				glDrawElements(GLConvert::Topology(_sprn->w_tpg), indexcount, indextype, NULL);

				// 바인드 해제
				_UnbindDraw();
			}
		}
	}
}

//
kvoid GLDevice::DrawGlyph(const tpl::Rect&bound, const tpl::ColorValue& color, qgTexture* tex)
{
	QGTRACE_DEVICE(GLDevice);

	k_return_if_ok(color.a < 2);

	// 그리기 준비
	_RenderOrtho(tex, color.a < 255, true, true);

	tpl::Quad qd = bound.ToQuad().Offset(-0.5f, -0.5f);

	// 그리기
	kGuardSpinLock(_dlock)
	{
		glBegin(GL_QUADS);

		glColor4ub(color.r, color.g, color.b, color.a);

		glTexCoord2f(_math::Global::TexCoord.left, _math::Global::TexCoord.top);
		glVertex2f(qd.left, qd.top);

		glTexCoord2f(_math::Global::TexCoord.right, _math::Global::TexCoord.top);
		glVertex2f(qd.right, qd.top);

		glTexCoord2f(_math::Global::TexCoord.right, _math::Global::TexCoord.bottom);
		glVertex2f(qd.right, qd.bottom);

		glTexCoord2f(_math::Global::TexCoord.left, _math::Global::TexCoord.bottom);
		glVertex2f(qd.left, qd.bottom);

		glEnd();
	}
}

//
kvoid GLDevice::DrawRect(const tpl::Rect& bound, const tpl::ColorValue& color, qgTexture* tex, const tpl::Quad* coord)
{
	QGTRACE_DEVICE(GLDevice);

	k_return_if_ok(color.a < 2);

	// 그리기 준비
	_RenderOrtho(tex, color.a < 255, true, false);

	tpl::Quad qd = bound.ToQuad().Offset(-0.5f, -0.5f);

	// 그리기
	kGuardSpinLock(_dlock)
	{
		glBegin(GL_QUADS);

		glColor4ub(color.r, color.g, color.b, color.a);

		if (tex)
		{
			if (!coord)
				coord = (const tpl::Quad*)&_math::Global::TexCoord;

			glTexCoord2f(coord->left, coord->top);
			glVertex2f(qd.left, qd.top);

			glTexCoord2f(coord->right, coord->top);
			glVertex2f(qd.right, qd.top);

			glTexCoord2f(coord->right, coord->bottom);
			glVertex2f(qd.right, qd.bottom);

			glTexCoord2f(coord->left, coord->bottom);
			glVertex2f(qd.left, qd.bottom);
		}
		else
		{
			glVertex2f(qd.left, qd.top);

			glVertex2f(qd.right, qd.top);

			glVertex2f(qd.right, qd.bottom);

			glVertex2f(qd.left, qd.bottom);
		}

		glEnd();
	}
}

//
kvoid GLDevice::DrawPolyLine(kint count, const tpl::Point pts[], const tpl::ColorValue colors[])
{
	QGTRACE_DEVICE(GLDevice);

	k_return_if_fail(count >= 2);
	k_return_if_fail(pts);

	// 그리기 준비
	_RenderOrtho(NULL, true, false, false);

	// 그리기... 아 정말 간단하네
	kGuardSpinLock(_dlock)
	{
		glBegin(GL_LINE_LOOP);

		for (kint i = 0; i < count; i++)
		{
			glColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
			glVertex2f((float)pts[i].x - 0.5f, (float)pts[i].y - 0.5f);
		}

		glEnd();
	}
}

//
void GLDevice::DrawPolyStrip(kint count, const tpl::Point* pts, const tpl::ColorValue* colors, qgTexture* tex, const tpl::Vec2* coords)
{
	QGTRACE_DEVICE(GLDevice);

	k_return_if_fail(pts);

	// 그리기 준비
	_RenderOrtho(tex, false, true, false);

	// 그리기
	kGuardSpinLock(_dlock)
	{
		glBegin(GL_TRIANGLE_STRIP);

		if (tex && coords)
		{
			if (colors)
			{
				for (kint i = 0; i < count; i++)
				{
					glColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
					glTexCoord2f(coords[i].u, coords[i].v);
					glVertex2f((float)pts[i].x - 0.5f, (float)pts[i].y - 0.5f);
				}
			}
			else
			{
				glColor4ub(255, 255, 255, 255);

				for (kint i = 0; i < count; i++)
				{
					glTexCoord2f(coords[i].u, coords[i].v);
					glVertex2f((float)pts[i].x - 0.5f, (float)pts[i].y - 0.5f);
				}
			}
		}
		else
		{
			if (colors)
			{
				for (kint i = 0; i < count; i++)
				{
					glColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
					glVertex2f((float)pts[i].x - 0.5f, (float)pts[i].y - 0.5f);
				}
			}
			else
			{
				glColor4ub(255, 255, 255, 255);

				for (kint i = 0; i < count; i++)
					glVertex2f((float)pts[i].x - 0.5f, (float)pts[i].y - 0.5f);
			}
		}

		glEnd();
	}
}

//
bool GLDevice::_SetRenderTarget(qgTexture* tex)
{
	// 거의 뷰포트만 처리한다
	tpl::Point size;

	if (tex)
	{
		GLTextureFBO* gltex = (GLTextureFBO*)tex;

		size.Set(gltex->_width, gltex->_height);
	}
	else
	{
		// 화면 그리기를 위한 장치는 별도로 없다
		size = _trfm.sd;
	}

	_vsize.Set((float)size.width, (float)size.height);

	_trfm.vp.loc.Set(0, 0);
	_trfm.vp.size = size;

	_trfm.otm = tpl::Mat4::OrthoLH(_vsize.width, -_vsize.height, 0.0f, 1.0f);
	_trfm.otm.ReplaceLocation(-1.0f, 1.0f, 0.0f);

	glViewport(0, 0, size.width, size.height);

	_RenderScene();

	return true;
}

//
void GLDevice::_RenderScene() throw()
{
	if (_vprn.r_mode != QGRMT_SCENE)
	{
#ifdef GL_EXT_clip_volume_hint
		// 볼륨 클립
		if (TestGLCS(QGLX_EXT_CLIP_VOLUME_HINT))
			glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_NICEST);
#endif

		// 변환 행렬
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(_trfm.tm[QGTMS_PROJ].ToPointer());

		_vprn.r_mode = QGRMT_SCENE;
	}
}

//
void GLDevice::_RenderOrtho(qgTexture* tex, bool hasalpha, bool haschannel, bool glyph) throw()
{
	// 직교 처리
	if (_vprn.r_mode != QGRMT_ORTHO)
	{
		// 공통 재질
		GLDevice::SetShader(NULL);
		GLDevice::SetRasz(_vdeb.rasz);

		if (glyph)
		{
			GLDevice::SetBlnd(_vdeb.bglyph);
			GLDevice::SetSplr(0, _vdeb.slinear);
		}
		else
		{
			GLDevice::SetBlnd(_vdeb.blnd);
			GLDevice::SetSplr(0, _vdeb.splr);
		}

#ifdef GL_EXT_clip_volume_hint
		// 볼륨 클립
		if (TestGLCS(QGLX_EXT_CLIP_VOLUME_HINT))
			glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_FASTEST);
#endif

		// 변환 행렬
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(_trfm.otm.ToPointer());

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glTranslatef(0.375f, 0.375f, 0.0f);	// 이거 뭐임? DT임? 걍 따라함

		// 모드
		_vprn.r_mode = QGRMT_ORTHO;
	}

	// 공통이 안되는거... 텍스쳐
	GLDevice::SetTexture(0, tex);
	GLDevice::SetTM(QGTMS_TEX_0, tpl::Mat4::IdentifyMatrix());

	// 바인드
	_BindMtrl(1);
	_BindShader();

	// 텍스쳐 속성
	if (!tex)
		_vprn.r_ortho_tex_attr = 0;
	else
	{
		if ((haschannel || tex->HasAlpha()))
		{
			if (_vprn.r_ortho_tex_attr != 2)
			{
				_vprn.r_ortho_tex_attr = 2;

#if !defined(GL_ARB_texture_env_combine) && !defined(GL_EXT_texture_env_combine)
				glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#else
				if (hasalpha)
					glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
				else
				{
#ifdef GL_ARB_texture_env_combine
					if (TestGLCS(QGLX_ARB_TEXTURE_ENV_COMBINE))
					{
						glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
					}
#else
					if (TestGLCS(QGLX_EXT_TEXTURE_ENV_COMBINE))
					{
						glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT);
					}
#endif
					else
						glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
				}
#endif
			}
		}
		else
		{
			if (_vprn.r_ortho_tex_attr != 1)
			{
				_vprn.r_ortho_tex_attr = 1;

#if !defined(GL_ARB_texture_env_combine) && !defined(GL_EXT_texture_env_combine)
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#else

				if (hasalpha)
				{
#ifdef GL_ARB_texture_env_combine
					if (TestGLCS(QGLX_ARB_TEXTURE_ENV_COMBINE))
					{
						glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
					}
#else
					if (TestGLCSQSGLX_EXT_TEXTURE_ENV_COMBINE))
					{
						glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PRIMARY_COLOR_EXT);
						glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
						glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT);
					}
#endif
					else
						glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
				}
				else
					glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#endif
			}
		}
	}
}

//
bool GLDevice::_SetClipPlane(kint nth, const tpl::Plane& plane, bool enable)
{
	k_return_value_if_fail((kbyte)nth < _caps.max_clip_planes, false);

	_plns[nth].plane = plane;
	_TurnClipPlane(nth, enable);

	return true;
}

//
void GLDevice::_TurnClipPlane(kint nth, bool enable)
{
	k_return_if_fail((kbyte)nth < _caps.max_clip_planes);

	if (enable)
	{
		if (!_plns[nth].enable)
		{
			_UpdateClipPlane(nth);
			glEnable(GL_CLIP_PLANE0 + nth);
			_plns[nth].enable = true;
		}
	}
	else
	{
		glDisable(GL_CLIP_PLANE0 + nth);
		_plns[nth].enable = false;
	}
}

//
void GLDevice::_UpdateClipPlane(kint nth)
{
	GLdouble d[4] =
	{
		_plns[nth].plane.a,
		_plns[nth].plane.b,
		_plns[nth].plane.c,
		_plns[nth].plane.d
	};

	glClipPlane(GL_CLIP_PLANE0 + nth, d);
}

#endif	// _QG_USE_GL


